﻿namespace DesktopRibbon.Helper
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using System.Windows;
    using System.Windows.Media;
    using DesktopRibbon.Launcher;
    using Microsoft.Windows.Controls.Ribbon;

    /// <summary>
    /// This class helps DesktopRibbonControl to handle Drag &amp; Drop.
    /// </summary>
    static class DragDropHelper
    {
        #region Fields

        private static Type [] ribbonControlTypes = new Type [] {
            typeof(RibbonButton)
        };

        #endregion

        #region PInvoke

        private struct POINT
        {
            public int X;
            public int Y;

            public Point ToPoint()
            {
                return new Point(X, Y);
            }
        }

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetCursorPos(out POINT lpPoint);

        #endregion

        #region Methods

        /// <summary>
        /// Used as a combined return value for GetRibbonControlUnderCursor.
        /// </summary>
        public class DragInfo
        {
            /// <summary>
            ///  The ribbon element under the cursor.
            /// </summary>
            public UIElement element;
            /// <summary>
            /// The corner where the mouse cursor position, relative to the ribbon element, is in.
            /// </summary>
            public Corner corner;
        }

        /// <summary>
        /// Finds a ribbon element that is under the mouse cursor. reference needs to be an UIElement that is a visual parent (or grandparent, etc.) of the Ribbon control.
        /// </summary>
        /// <param name="reference">An UIElement that is the parent (or grandparent, etc.) of the Ribbon control that can possible be under the cursor.</param>
        /// <returns>The return value contains the element that is found under the cursor. element is guaranteed to be a Ribbon element such as RibbonButton, RibbonGroup, etc.
        /// See <see>DragInfo</see> for more details.</returns>
        public static DragInfo GetRibbonControlUnderCursor(UIElement reference)
        {
            // Mouse.GetPosition is buggy while drag & drop operations!
            // You can see others complain about this here:
            // var mousePos = Mouse.GetPosition(reference);
            var mousePos = new POINT();
            var cursorPosReceived = GetCursorPos(out mousePos);
            System.Diagnostics.Debug.Assert(cursorPosReceived);
            var relativePos = reference.PointFromScreen(mousePos.ToPoint ());
            var hitTestResult = VisualTreeHelper.HitTest(reference, relativePos);
            if (null != hitTestResult)
            {
                var hit = hitTestResult.VisualHit as FrameworkElement;
                if (null != hit)
                {
                    var element = FindParentAnyTypeOf(hit, ribbonControlTypes);
                    var corner = Corner.Top;
                    if (null != element)
                    {
                        var elemPos = element.PointFromScreen(mousePos.ToPoint());
                        if (elemPos.X < (element.ActualWidth / 2))
                            corner = Corner.Left;
                        else
                            corner = Corner.Right;
                    }
                    return new DragInfo() { element = element, corner = corner };
                }
            }
            return new DragInfo();
        }

        /// <summary>
        /// Finds a visual parent of element that is of any type contained in types.
        /// </summary>
        /// <param name="element">The FrameworkElement who's visual parent should be found.</param>
        /// <param name="types">A list of types which are OK to return.</param>
        /// <returns>A FrameworkElement that is guaranteed to be of any type specified in types.</returns>
        public static FrameworkElement FindParentAnyTypeOf(FrameworkElement element, Type [] types)
        {
            if (element == null)
            {
                return null;
            }

            if (IsAnyTypeOf(element, types))
            {
                return element;
            }

            // Note: We do not use element.Parent here, because the logical parent differs from the visual parent.
            var parent = VisualTreeHelper.GetParent(element) as FrameworkElement;
            if (parent == null)
            {
                return null;
            }
            else
            {
                return FindParentAnyTypeOf(parent, types);
            }
        }

        /// <summary>
        /// Creates an ILauncherItem from the data in a drag event (most likely the Dropped event).
        /// </summary>
        /// <param name="iconCache">The icon cache that the ILauncherItem should use to load it's icons from.</param>
        /// <param name="dragArgs">The drag data provided from the drag event.</param>
        /// <returns>An ILauncherItem, if dragArgs contains data compatible with the Desktop Ribbon, or null.</returns>
        public static LauncherItem CreateLauncherItem(IconCache.IconCache iconCache, DragEventArgs dragArgs)
        {
            var data = dragArgs.Data;
            var formats = data.GetFormats(true);
            var formatList = new List<string>(formats);
            if (formatList.IndexOf("FileName") >= 0)
            {
                var dragData = data.GetData("FileName") as string [];
                if (null != dragData && dragData.Length > 0)
                {
                    var fileName = dragData [0];
                    var result = new ShellLauncherItem(iconCache, fileName);
                    return result;
                }
            }
            return null;
        }
        
        private static bool IsAnyTypeOf(object obj, Type [] types)
        {
            foreach (var type in types)
            {
                if (obj.GetType().Equals(type))
                    return true;
            }
            return false;
        }

        #endregion
    }
}
